/*
  Copyright (c) 2010
  The Regents of the University of Michigan
  All Rights Reserved

  Permission is granted to use, copy, create derivative works, and
  redistribute this software and such derivative works for any purpose,
  so long as the name of the University of Michigan is not used in
  any advertising or publicity pertaining to the use or distribution
  of this software without specific, written prior authorization. If
  the above copyright notice or any other identification of the
  University of Michigan is included in any copy of any portion of
  this software, then the disclaimer below must also be included.

  This software is provided as is, without representation or warranty
  of any kind either express or implied, including without limitation
  the implied warranties of merchantability, fitness for a particular
  purpose, or noninfringement.  The Regents of the University of
  Michigan shall not be liable for any damages, including special,
  indirect, incidental, or consequential damages, with respect to any
  claim arising out of or in connection with the use of the software,
  even if it has been or is hereafter advised of the possibility of
  such damages.
*/
#include "network.h"

#include <assert.h>
#include <errno.h>
#include <openssl/rand.h>
#include <openssl/sha.h>
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>

#include "clearview.h"
#include "clearview_internal.h"
#include "serialization.h"
#include "utils.h"

extern uint32_t next_transaction_id;
extern struct in_addr local_addr;


static uint64_t get_connection_id(void) {
  uint64_t ret;
  RAND_bytes((unsigned char *)&ret, sizeof(ret));
  return ret | 0x8000000000000000ULL;
}


static inline uint64_t get_next_transaction_id(void) {
  return next_transaction_id++;
}


static void clearview_build_request_header(
    const struct ClearviewInstance *sender, struct RequestPacketHeader *header) {
  struct timeval tv;
  header->connection_id = get_connection_id();
  header->transaction_id = get_next_transaction_id();
  header->protocol_version = SUPPORTED_PROTOCOL_VERSION;
  header->vendor_id = 0;
  header->network_id = 0;
  header->local_protocol_version = SUPPORTED_PROTOCOL_VERSION;
  header->node_address.addr = local_addr;
  header->node_address.port = sender->port;
  header->instance_id = sender->instance_id;

  gettimeofday(&tv, NULL);
  header->time = ((uint64_t)tv.tv_sec) * 1000 + tv.tv_usec / 1000;
}


static inline int clearview_send(const struct ClearviewInstance *sender,
                                 const struct ContactAddress *contact, char *buf,
                                 size_t buflen) {
  struct sockaddr_in sa;
  g_assert(sender);
  g_assert(sender->sock > 0);
  g_assert(contact);
  g_assert(buf);
  g_assert(buflen > 0);

  sa.sin_family = AF_INET;
  sa.sin_port = htons(contact->port);
  sa.sin_addr = contact->addr;

/*
#ifdef VERBOSE_LOG
  fprintf(stderr, "clearview_send to %s:%d", inet_ntoa(sa.sin_addr), ntohs(sa.sin_port));
  print_hex((unsigned char *)buf, buflen);
#endif
*/
  // TODO(swolchok): do we need to worry about and recover from EINTR here?
  if (sendto(sender->sock, buf, buflen, 0, (struct sockaddr *)&sa,
             sizeof(sa)) == -1) {
    fprintf(stderr, "Got error while sending packet to %s:%hu : %s\n",
            inet_ntoa(sa.sin_addr), contact->port, strerror(errno));
    return -1;
  }

  return 0;
}


int clearview_send_ping_request(const struct ClearviewInstance *sender,
                                const struct ContactAddress *contact) {
  struct RequestPingPacket pkt;
  char *buf;
  size_t buflen;

  clearview_build_request_header(sender, &pkt.header);
  pkt.header.action = ACT_REQUEST_PING;
  encode_ping_request(&pkt, &buf, &buflen);
  RETURN_IF_NEGATIVE(clearview_send(sender, contact, buf, buflen));
  return 0;
}


int clearview_send_find_node_request(const struct ClearviewInstance *sender,
                                     const struct ContactAddress *contact,
                                     char id[KEY_SIZE]) {
  struct RequestFindNodePacket pkt;
  char *buf;
  size_t buflen;

  clearview_build_request_header(sender, &pkt.header);
  pkt.header.action = ACT_REQUEST_FIND_NODE;
  memcpy(pkt.id, id, KEY_SIZE);
  pkt.node_status = 0xFFFFFFFF; // Unknown node status.

  encode_find_node_request(&pkt, &buf, &buflen);
  RETURN_IF_NEGATIVE(clearview_send(sender, contact, buf, buflen));
  return 0;
}


static void clearview_build_reply_header(
    const struct ClearviewInstance *sender,
    const struct RequestPacketHeader *request,
    struct ReplyPacketHeader *out) {
  out->transaction_id = request->transaction_id;
  out->connection_id = request->connection_id;
  out->protocol_version = SUPPORTED_PROTOCOL_VERSION;
  out->vendor_id = 0;
  out->network_id = 0;
  out->instance_id = sender->instance_id;
}


int clearview_send_ping_reply(const struct ClearviewInstance *sender,
                              const struct RequestPacketHeader *request) {
  struct ReplyPingPacket pkt;
  char *buf;
  size_t buflen;
  clearview_build_reply_header(sender, request, &pkt.header);
  pkt.header.action = ACT_REPLY_PING;

  encode_ping_reply(&pkt, &buf, &buflen);
  RETURN_IF_NEGATIVE(clearview_send(sender, &request->node_address, buf,
                                    buflen));
  return 0;
}


int clearview_send_empty_store_reply(
    const struct ClearviewInstance *sender,
    const struct RequestPacketHeader *request) {
  struct ReplyStorePacket pkt;
  char *buf;
  size_t buflen;

  clearview_build_reply_header(sender ,request, &pkt.header);
  pkt.header.action = ACT_REPLY_STORE;

  pkt.diversifications_length = 0;
  pkt.diversifications = NULL;

  encode_store_reply(&pkt, &buf, &buflen);
  RETURN_IF_NEGATIVE(clearview_send(sender, &request->node_address, buf,
                                    buflen));
  return 0;
}

int clearview_send_unhelpful_find_node_reply(
    const struct ClearviewInstance *sender,
    const struct RequestPacketHeader *request) {
  struct ReplyFindNodePacket pkt;
  char *buf;
  size_t buflen;
  struct ContactAddress contact = {NULL, local_addr, sender->port};
  struct ContactAddress *dummy_contacts = {&contact};
  
  contact_init(&contact);

  clearview_build_reply_header(sender, request, &pkt.header);
  pkt.header.action = ACT_REPLY_FIND_NODE;
  // This is research code -- don't need anti-spoof.
  // (Besides, who is running a client that doesn't protect against this? ;) )
  pkt.spoof_id = 0xFACEFEED;
  pkt.node_type = ORDINARY;
  pkt.dht_size = 0;  // Who knows?
  pkt.contacts_count = 1;
  pkt.contacts = &dummy_contacts;

  encode_find_node_reply(&pkt, &buf, &buflen);
  RETURN_IF_NEGATIVE(clearview_send(sender, &request->node_address, buf,
                                    buflen));
  return 0;
}
